Pipfile.lockで固定された依存関係を再現するならpipenv syncコマンドを使おう
こんにちは、CX事業本部の若槻です。
今回は、Pipenvで仮想環境を再現するコマンドを色々と触ってみたところ、「誰かが作成したPipfile.lock
をもとに、別のメンバーが同じ依存関係(パッケージバージョン)の環境を再現するならpipenv syncコマンドを使えば良さそう!」という結論に至ったのでご紹介します。
Pipenvとは
Pipenv is a tool that aims to bring the best of all packaging worlds (bundler, composer, npm, cargo, yarn, etc.) to the Python world. Windows is a first-class citizen, in our world.
It automatically creates and manages a virtualenv for your projects, as well as adds/removes packages from your Pipfile as you install/uninstall packages. It also generates the ever-important Pipfile.lock, which is used to produce deterministic builds.
Pipenv is primarily meant to provide users and developers of applications with an easy method to setup a working environment.
Pipenvは、virtualenvのような仮想環境の管理と、Pipのようなパッケージ管理の機能を備えたツールです。
PipenvではPipfile
とPipfile.lock
を利用することにより、Pipでrequirements.txt
を用いるよりも強力なパッケージ管理を行うことができます。
Pipfile
:要求されたパッケージの一覧などが記録されるファイル。Pipfile.lock
:実際にインストールされるパッケージの依存関係などが記録されるファイル。
試しにpipenv install
コマンドでrequestsのバージョン2.20.0
をインストールしてみると、Pipenvの仮想環境にrequestsがインストールされ、同時にPipfile
とPipfile.lock
が作成されました。(Pipenvによるパッケージインストールが初めて行われるディレクトリ内であれば、仮想環境の作成も同時に行われます。)
$ pipenv install requests==2.20.0 Installing requests==2.20.0… Adding requests to Pipfile's [packages]… ✔ Installation Succeeded Pipfile.lock (870504) out of date, updating to (ca72e7)… Locking [dev-packages] dependencies… Locking [packages] dependencies… ✔ Success! Updated Pipfile.lock (870504)! Installing dependencies from Pipfile.lock (870504)… ▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉ 5/5 — 00:00:03 To activate this project's virtualenv, run pipenv shell. Alternatively, run a command inside the virtualenv with pipenv run.
作成されたPipfileです。要求されたパッケージrequests==2.20.0
が記録されています。
[source] name = "pypi" url = "https://pypi.org/simple" verify_ssl = true [dev-packages] [packages] requests = "==2.20.0" [requires] python_version = "3.6"
作成されたPipfile.lockです。要求されたrequests==2.20.0
に依存してインストールされたパッケージのバージョンが記録されています。(このことをパッケージの依存関係の固定と言います。)
{ "_meta": { "hash": { "sha256": "5c2429960803a0b6159d6589fa3dd25d89557b2497344392443403d5cb870504" }, "pipfile-spec": 6, "requires": { "python_version": "3.6" }, "sources": [ { "name": "pypi", "url": "https://pypi.org/simple", "verify_ssl": true } ] }, "default": { "certifi": { "hashes": [ "sha256:1d987a998c75633c40847cc966fcf5904906c920a7f17ef374f5aa4282abd304", "sha256:51fcb31174be6e6664c5f69e3e1691a2d72a1a12e90f872cbdb1567eb47b6519" ], "version": "==2020.4.5.1" }, "chardet": { "hashes": [ "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" ], "version": "==3.0.4" }, "idna": { "hashes": [ "sha256:156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e", "sha256:684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16" ], "version": "==2.7" }, "requests": { "hashes": [ "sha256:99dcfdaaeb17caf6e526f32b6a7b780461512ab3f1d992187801694cba42770c", "sha256:a84b8c9ab6239b578f22d1c21d51b696dcfe004032bb80ea832398d6909d7279" ], "index": "pypi", "version": "==2.20.0" }, "urllib3": { "hashes": [ "sha256:2393a695cd12afedd0dcb26fe5d50d0cf248e5a66f75dbd89a3d4eb333a61af4", "sha256:a637e5fae88995b256e3409dc4d52c2e2e0ba32c42a6365fee8bbd2238de3cfb" ], "version": "==1.24.3" } }, "develop": {} }
開発プロジェクトですでに誰かが作成したPipfile
やPipfile.lock
を使えば、他のメンバーも同じPipenv仮想環境を簡単に再現できるようになります。
PipfileやPipfile.lockから環境を再現するコマンド
PipfileやPipfile.lockから環境を再現する場合は、ドキュメント[Pipenv: Python Dev Workflow for Humans]によると以下のようなコマンドが使われるようです。
pipenv install
pipenv install --deploy
pipenv install --ignore-pipfile
pipenv sync
しかし、ドキュメントを読んでもこれらコマンドの使い分けまではよく分からなかったため、以下のような状況を想定したPipfile
とPipfile.lock
を用いて、urllib3
が未インストールの環境でのコマンド実行時の動作を確認してみました。
- 想定した状況
pipenv install urllib3
によりある時点でのurllib3の最新バージョン(1.25
)をインストールして以下のPipfileとPipfile.lockを作った。- その後urllib3のアップデートがリリースされ、現在の最新バージョンは
1.25.9
である。
[source] name = "pypi" url = "https://pypi.org/simple" verify_ssl = true [dev-packages] [packages] urllib3 = "*" [requires] python_version = "3.6"
{ "_meta": { "hash": { "sha256": "14f78c3f58762bc3ff8c948eaf6c463fb56183863d6f90d28f2cf48bf6053760" }, "pipfile-spec": 6, "requires": { "python_version": "3.6" }, "sources": [ { "name": "pypi", "url": "https://pypi.org/simple", "verify_ssl": true } ] }, "default": { "urllib3": { "hashes": [ "sha256:a08afe8b057ba35963364711a1f36d346375da0c118f611f35c0252375338c7c", "sha256:f03eeb431c77b88cf8747d47e94233a91d0e0fdae1cf09e0b21405a885700266" ], "index": "pypi", "version": "==1.25" } }, "develop": {} }
なお、コマンド毎にPipenv環境は作り直しています。
pipenv install
pipenv install
コマンドでインストールを行った場合は、Pipfile
を元にして、urllib3
の最新バージョン1.25.9
がインストールされ、Pipfile.lockも更新される動作となりました。
$ pipenv install Pipfile.lock (b8b71b) out of date, updating to (053760)… Locking [dev-packages] dependencies… Locking [packages] dependencies… ✔ Success! Updated Pipfile.lock (b8b71b)! Installing dependencies from Pipfile.lock (b8b71b)… ▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉ 1/1 — 00:00:00 To activate this project's virtualenv, run pipenv shell. Alternatively, run a command inside the virtualenv with pipenv run.
$ pipenv graph urllib3==1.25.9 $ git diff diff --git a/Pipfile.lock b/Pipfile.lock index 065770e..dffb44c 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "14f78c3f58762bc3ff8c948eaf6c463fb56183863d6f90d28f2cf48bf6053760" + "sha256": "fe045e48581ffc6cb50af922ec04120b504f96ff6d6917fc535c8a9b76b8b71b" }, "pipfile-spec": 6, "requires": { @@ -18,11 +18,11 @@ "default": { "urllib3": { "hashes": [ - "sha256:a08afe8b057ba35963364711a1f36d346375da0c118f611f35c0252375338c7c", - "sha256:f03eeb431c77b88cf8747d47e94233a91d0e0fdae1cf09e0b21405a885700266" + "sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527", + "sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115" ], "index": "pypi", - "version": "==1.25" + "version": "==1.25.9" } }, "develop": {}
このときのPipfile
、Pipfile.lock
上の記録、および実際に環境上にインストールされたurllib3
のバージョンのコマンド実行前後の状態を表であらわすと下記のようになります。
urllib3 のバージョン |
コマンド実行前 | コマンド実行後 |
---|---|---|
pipenv install |
||
Pipfile | * |
* |
Pipfile.lock | 1.25 |
1.25.9 |
実際の環境上 | なし | 1.25.9 |
pipenv install --deploy
pipenv install --deploy
コマンドでインストールを行おうとした場合は、「Your Pipfile.lock (053760) is out of date.」というエラーによりurllib3
のインストールはされず、PipfileとPipfile.lockの更新もされませんでした。
$ pipenv install --deploy Your Pipfile.lock (053760) is out of date. Expected: (b8b71b). Traceback (most recent call last): File "/usr/local/bin/pipenv", line 11, in <module> sys.exit(cli()) File "/usr/local/lib/python3.6/site-packages/pipenv/vendor/click/core.py", line 764, in __call__ return self.main(*args, **kwargs) File "/usr/local/lib/python3.6/site-packages/pipenv/vendor/click/core.py", line 717, in main rv = self.invoke(ctx) File "/usr/local/lib/python3.6/site-packages/pipenv/vendor/click/core.py", line 1137, in invoke return _process_result(sub_ctx.command.invoke(sub_ctx)) File "/usr/local/lib/python3.6/site-packages/pipenv/vendor/click/core.py", line 956, in invoke return ctx.invoke(self.callback, **ctx.params) File "/usr/local/lib/python3.6/site-packages/pipenv/vendor/click/core.py", line 555, in invoke return callback(*args, **kwargs) File "/usr/local/lib/python3.6/site-packages/pipenv/vendor/click/decorators.py", line 64, in new_func return ctx.invoke(f, obj, *args, **kwargs) File "/usr/local/lib/python3.6/site-packages/pipenv/vendor/click/core.py", line 555, in invoke return callback(*args, **kwargs) File "/usr/local/lib/python3.6/site-packages/pipenv/vendor/click/decorators.py", line 17, in new_func return f(get_current_context(), *args, **kwargs) File "/usr/local/lib/python3.6/site-packages/pipenv/cli/command.py", line 254, in install editable_packages=state.installstate.editables, File "/usr/local/lib/python3.6/site-packages/pipenv/core.py", line 1874, in do_install keep_outdated=keep_outdated File "/usr/local/lib/python3.6/site-packages/pipenv/core.py", line 1192, in do_init raise exceptions.DeployException File "/usr/local/lib/python3.6/site-packages/pipenv/exceptions.py", line 168, in __init__ PipenvUsageError.__init__(message=fix_utf8(message), extra=extra, **kwargs) TypeError: __init__() missing 1 required positional argument: 'self'
$ pipenv graph $ $ git diff $
このときのコマンド実行前後の状態を表であらわすと下記のようになります。
urllib3 のバージョン |
コマンド実行前 | コマンド実行後 |
---|---|---|
pipenv install --deploy |
||
Pipfile | * |
* |
Pipfile.lock | 1.25 |
1.25 |
実際の環境上 | なし | なし |
pipenv install --deploy
コマンドに関してurllib3ドキュメントの[☤ Using pipenv for Deployments]ページによると、
You can enforce that your Pipfile.lock is up to date using the --deploy flag:
$ pipenv install --deploy
This will fail a build if the Pipfile.lock is out–of–date, instead of generating a new one.
とあり、このコマンドは「Pipfileをもとにパッケージをインストールするが、Pipfile.lockがout-of-dateの場合は失敗する」とのことです。(よって実質的にPipfile.lockの最新化が強制されるようです。)
ここで、pipenv lock
コマンドを実行して、PipfileをもとにPipfile.lockを最新化してみます。
$ pipenv lock Locking [dev-packages] dependencies… Locking [packages] dependencies… ✔ Success! Updated Pipfile.lock (b8b71b)!
$ git diff diff --git a/Pipfile.lock b/Pipfile.lock index 065770e..dffb44c 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "14f78c3f58762bc3ff8c948eaf6c463fb56183863d6f90d28f2cf48bf6053760" + "sha256": "fe045e48581ffc6cb50af922ec04120b504f96ff6d6917fc535c8a9b76b8b71b" }, "pipfile-spec": 6, "requires": { @@ -18,11 +18,11 @@ "default": { "urllib3": { "hashes": [ - "sha256:a08afe8b057ba35963364711a1f36d346375da0c118f611f35c0252375338c7c", - "sha256:f03eeb431c77b88cf8747d47e94233a91d0e0fdae1cf09e0b21405a885700266" + "sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527", + "sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115" ], "index": "pypi", - "version": "==1.25" + "version": "==1.25.9" } }, "develop": {}
このときのコマンド実行前後の状態を表であらわすと下記のようになります。
urllib3 のバージョン |
コマンド実行前 | コマンド実行後 |
---|---|---|
pipenv lock |
||
Pipfile | * |
* |
Pipfile.lock | 1.25 |
1.25.9 |
実際の環境上 | なし | なし |
そしてこの状態で再度pipenv install --deploy
コマンドを実行すると、urllib3
の最新バージョン1.25.9
がインストールされました。
$ pipenv install --deploy Installing dependencies from Pipfile.lock (b8b71b)… ▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉ 1/1 — 00:00:00
このときのpipenv install --deploy
コマンド実行前後の状態を表であらわすと下記のようになります。
urllib3 のバージョン |
コマンド実行前 | コマンド実行後 |
---|---|---|
pipenv install --deploy |
||
Pipfile | * |
* |
Pipfile.lock | 1.25.9 |
1.25.9 |
実際の環境上 | なし | 1.25.9 |
pipenv install --ignore-pipfile
pipenv install --ignore-pipfile
コマンドでインストールを行った場合は、Pipfile.lock
を元にして、urllib3
のバージョン1.25
がインストールされ、一方でPipfileとPipfile.lockの更新は行われない動作となりました。
$ pipenv graph urllib3==1.25 $ pipenv install --ignore-pipfile Installing dependencies from Pipfile.lock (053760)… ▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉ 1/1 — 00:00:00
このときのコマンド実行前後の状態を表であらわすと下記のようになります。
urllib3 のバージョン |
コマンド実行前 | コマンド実行後 |
---|---|---|
pipenv install --ignore-pipfile |
||
Pipfile | * |
* |
Pipfile.lock | 1.25 |
1.25 |
実際の環境上 | なし | 1.25 |
pipenv install --ignore-pipfile
コマンドに関してurllib3ドキュメントの[☤ Testing Projects]ページによると、
You might also want to add --ignore-pipfile to pipenv install, as to not accidentally modify the lock-file on each test run. This causes Pipenv to ignore changes to the Pipfile and (more importantly) prevents it from adding the current environment to Pipfile.lock.
とあり、このコマンドは「PipfileではなくPipfile.lockをもとにパッケージをインストールし、また現在の環境をもとにPipfile.lockの更新は行わない」とのことです。
では少し意地悪をして、pipenv install urllib3==1.25.1 --ignore-pipfile
のように明示的にバージョンを指定して実行してみると、urllib3==1.25.1
がインストールされ、PipfileとPipfile.lockが更新されました。
$ pipenv install urllib3==1.25.1 --ignore-pipfile Installing urllib3==1.25.1… Adding urllib3 to Pipfile's [packages]… ✔ Installation Succeeded Pipfile.lock (02301d) out of date, updating to (053760)… Locking [dev-packages] dependencies… Locking [packages] dependencies… ✔ Success! Updated Pipfile.lock (02301d)! Installing dependencies from Pipfile.lock (02301d)… ▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉ 1/1 — 00:00:00 To activate this project's virtualenv, run pipenv shell. Alternatively, run a command inside the virtualenv with pipenv run.
$ pipenv graph urllib3==1.25.1 $ git diff diff --git a/Pipfile b/Pipfile index 9b6677f..3fe27a9 100644 --- a/Pipfile +++ b/Pipfile @@ -6,7 +6,7 @@ verify_ssl = true [dev-packages] [packages] -urllib3 = "*" +urllib3 = "==1.25.1" [requires] python_version = "3.6" diff --git a/Pipfile.lock b/Pipfile.lock index 065770e..e55a1b6 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "14f78c3f58762bc3ff8c948eaf6c463fb56183863d6f90d28f2cf48bf6053760" + "sha256": "a83dd596857a98d568970ad6bbd39fbe838378d5b620c1924a0993518402301d" }, "pipfile-spec": 6, "requires": { @@ -18,11 +18,11 @@ "default": { "urllib3": { "hashes": [ - "sha256:a08afe8b057ba35963364711a1f36d346375da0c118f611f35c0252375338c7c", - "sha256:f03eeb431c77b88cf8747d47e94233a91d0e0fdae1cf09e0b21405a885700266" + "sha256:904bd981d6371bb95a200c0ec9dba5ba7cc980f2d6b125bd793fefe3293be388", + "sha256:a9645efd62b9fc1c7cad8ed93e162aad4c6bfd90e143966ddd4099b78cd244be" ], "index": "pypi", - "version": "==1.25" + "version": "==1.25.1" } }, "develop": {}
このときのコマンド実行前後の状態を表であらわすと下記のようになります。
urllib3 のバージョン |
コマンド実行前 | コマンド実行後 |
---|---|---|
pipenv install urllib3==1.25.1 --ignore-pipfile |
||
Pipfile | * |
1.25.1 |
Pipfile.lock | 1.25 |
1.25.1 |
実際の環境上 | なし | 1.25.1 |
pipenv sync
pipenv sync
コマンドでインストールを行った場合は、Pipfile.lock
を元にして、urllib3
のバージョン1.25
がインストールされ、一方でPipfileとPipfile.lockの更新は行われない動作となりました。
$ pipenv sync Installing dependencies from Pipfile.lock (053760)… ▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉ 1/1 — 00:00:00
$ pipenv graph urllib3==1.25 $ git diff $
このときのコマンド実行前後の状態を表であらわすと下記のようになります。
urllib3 のバージョン |
コマンド実行前 | コマンド実行後 |
---|---|---|
pipenv sync |
||
Pipfile | * |
* |
Pipfile.lock | 1.25 |
1.25 |
実際の環境上 | なし | 1.25 |
pipenv sync
コマンドに関してurllib3ドキュメントの[☤ Using pipenv for Deployments]ページによると、
pipenv install --ignore-pipfile is nearly equivalent to pipenv sync, but pipenv sync will never attempt to re-lock your dependencies as it is considered an atomic operation.
とあり、このコマンドはpipenv install --ignore-pipfile
のように「Pipfile.lockをもとにインストールを行い、re-lock(再ロック:Pipfile.lock上の依存関係の記録を更新すること)は行わない」とのことです。
ちなみにこのコマンドでもpipenv sync urllib3==1.25.1
のようにして実行を試してみましたが、オプションとしてパッケージの指定はできずエラーとなりました。このことからpipenv sync
は飽くまでPipfile.lockで固定した依存関係を再現するためのコマンドであるようです。
まとめ
ここまで確認したコマンドごとの実行前後の状態の表をまとめると下記のようになります。
urllib3 のバージョン |
コマンド実行前 | コマンド実行後 |
---|---|---|
pipenv install |
||
Pipfile | * |
* |
Pipfile.lock | 1.25 |
1.25.9 |
実際の環境上 | なし | 1.25.9 |
pipenv install --deploy |
||
Pipfile | * |
* |
Pipfile.lock | 1.25 |
1.25 |
実際の環境上 | なし | なし |
pipenv lock |
||
Pipfile | * |
* |
Pipfile.lock | 1.25 |
1.25.9 |
実際の環境上 | なし | なし |
pipenv install --deploy |
||
Pipfile | * |
* |
Pipfile.lock | 1.25.9 |
1.25.9 |
実際の環境上 | なし | 1.25.9 |
pipenv install --ignore-pipfile |
||
Pipfile | * |
* |
Pipfile.lock | 1.25 |
1.25 |
実際の環境上 | なし | 1.25 |
pipenv install urllib3==1.25.1 --ignore-pipfile |
||
Pipfile | * |
1.25.1 |
Pipfile.lock | 1.25 |
1.25.1 |
実際の環境上 | なし | 1.25.1 |
pipenv sync |
||
Pipfile | * |
* |
Pipfile.lock | 1.25 |
1.25 |
実際の環境上 | なし | 1.25 |
また、上記確認結果やドキュメントをもとに、PipfileやPipfile.lockから環境を再現するコマンドの使い分けをまとめると以下のようになりました。
pipenv install
- Pipfileからインストール
- 再ロックする
pipenv install --deploy
- Pipfileからインストール
- 再ロックしない
- Pipfile.lockがout-of-dateの場合は失敗する(実質的にPipfile.lockの最新化を強制する)
pipenv install --ignore-pipfile
- Pipfile.lockからインストール
- 再ロックしない(※パッケージを指定した場合を除く)
pipenv sync
- Pipfile.lockからインストール
- 再ロックしない
以上のまとめより、「誰かが作成したPipfile.lock
をもとに、別のメンバーが同じ依存関係(パッケージバージョン)の環境を再現するならpipenv syncコマンドを使えば良さそう!」という結論に至りました。
参考
- Pipenv: Python Dev Workflow for Humans
- pyenv、pyenv-virtualenv、venv、Anaconda、Pipenv。私はPipenvを使う。 - Qiita
- Pipenvを使ったPython開発まとめ - Qiita
以上